#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/regmap.h>
#include <linux/spi/spi.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include<linux/interrupt.h>
#include <linux/device.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/clk.h>

#include <sound/core.h>
#include <sound/soc.h>
#include <sound/initval.h>

#include "SI3050REGS.h"

static int cs;
static int clk;
static int dout;
static int din;
static int reset_pin;
static int irq_pin;
static int irq;
static struct clk *mclk;
extern void WriteRegister(u8 chip_id,u8 chan, u8 addr, u8 dat);
extern void ReadRegister(u8 chip_id,u8 chan, u8 addr, u8 *dat);

/**
 * dst配置信息如下
	si3050 {
		compatible = "mediatek,mid";
		reset = <&gpio3 RK_PC3 GPIO_ACTIVE_LOW>;
		cs = <&gpio3 RK_PB2 GPIO_ACTIVE_LOW>;
		clk = <&gpio3 RK_PB7 GPIO_ACTIVE_HIGH>;
#if 0
		din = <&gpio3 RK_PB4 GPIO_ACTIVE_HIGH>;
		dout = <&gpio3 RK_PB6 GPIO_ACTIVE_HIGH>;
#else
		dout = <&gpio3 RK_PB4 GPIO_ACTIVE_HIGH>;
		din = <&gpio3 RK_PB6 GPIO_ACTIVE_HIGH>;
#endif
		clocks = <&cru SCLK_I2S2_OUT>;
		clock-names = "mclk";
		pinctrl-names = "default";
		pinctrl-0 = <&i2s2_2ch_mclk>;
		#sound-dai-cells = <0>;
	};

 */


void Si3050_init(u8 chip_id,u8 channel_id)
{
	// tj add extra init @{
	WriteRegister(chip_id,channel_id,CTRL2,0xc3);
	WriteRegister(chip_id,channel_id,INTE_MSK,0xfe);
	// @}
	WriteRegister(chip_id,channel_id,INTL_CTRL1,0x11);  //Register 16 = 11  # IIR Filter	RT=1, 19.35 Vrms 23.65 VRMS
	WriteRegister(chip_id,channel_id,INTL_CTRL2,0x02);
	WriteRegister(chip_id,channel_id,DC_TERM_CTRL,0xC0);  //Register 26 = C0  # DCV=11 MINI=00
	WriteRegister(chip_id,channel_id,DAA_CTRL5,0xa2);  //#Register 31 = A2	 # FULL=1 FOH=128ms  FILT=1 HPF2=0 (RX=180Hz)  =a3可解决反向摘机时，极性错误，但开短路挂机不为0
	WriteRegister(chip_id,channel_id,DAA_CTRL1,0x08);	 //		 #on-hk
	/*############铃流校验#################*/
	WriteRegister(chip_id,channel_id,RNG_VLD_CTRL1,0x16);
	WriteRegister(chip_id,channel_id,RNG_VLD_CTRL2,0x09);
	WriteRegister(chip_id,channel_id,RNG_VLD_CTRL3,0x99);
	/*########### R200_680_104  #######*/
	//WriteRegister(chip_id,channel_id,AC_TERM_CTRL,0x0a);
	//WriteRegister(chip_id,channel_id,TX_GN_CTRL3,0x15);
	//AD:0DB  DA:-3.5
	//WriteRegister(chip_id,channel_id,TX_GN_CTRL2,0x12);
	//WriteRegister(chip_id,channel_id,TX_GN_CTRL3,0x15);
	//WriteRegister(chip_id,channel_id,RX_GN_CTRL2,0x00);
	//WriteRegister(chip_id,channel_id,RX_GN_CTRL3,0x08);

	WriteRegister(chip_id,channel_id,SPRK_QNCH_CTRL,0x00);  //On-Hook Speed,Less than 0.5 ms
	WriteRegister(chip_id,channel_id,DAA_CTRL2,0x00);	 //Register 6 = 00		 # power up lineside
	//WriteRegister(chip_id,channel_id,DAA_CTRL1,0x09);	 //		 #on-hk
	WriteRegister(chip_id,channel_id,DAA_CTRL1,0x08);	 //		 #off-hk
	//printf ("init si350.......\n\n");
}


void init_8FXO(void)
{
	WriteRegister(0,0,CTRL1,0x80);
	msleep(100);  //WAIT 100
	Si3050_init(0,0);
}
void KT143B_PCM_set(void)
{
	/*pcm mode*/
	WriteRegister(0,0,PCM_SPI_CTRL,0x20);
	WriteRegister(0,0,34,0x00);
	WriteRegister(0,0,36,0x08);                //ch1fxo

//	WriteRegister(0,1,34,0x08);
//	WriteRegister(0,1,36,0x00);                //ch2 fxo
}

void si3050_setup(void)
{
	init_8FXO();
	KT143B_PCM_set();
}

static inline void cs_low(void)
{
	 gpio_set_value(cs, 0);
}
static inline void cs_high(void)
{
	 gpio_set_value(cs, 1);
}

static inline void clk_low(void)
{
	 gpio_set_value(clk, 0);
}
static inline void clk_high(void)
{
	 gpio_set_value(clk, 1);
}

static inline void dout_low(void)
{
	 gpio_set_value(dout, 0);
}
static inline void dout_high(void)
{
	 gpio_set_value(dout, 1);
}

static inline void din_low(void)
{
	 gpio_direction_output(din, 0);
}
static inline void din_high(void)
{
	 gpio_direction_output(din, 1);
}

static inline int get_din(void)
{
	return gpio_get_value(din);
}


void spi_write_byte(u8 dat)
{
	int i;
	cs_low();
	clk_low();
	for(i=0; i<8; i++){
		udelay(4);
		clk_low();
		if(dat & 0x80){
			dout_high();
		}else{
			dout_low();
		}
		udelay(3);
		clk_high();
		dat <<= 1;
	}
	cs_high();
}


u8 spi_read_byte(void)
{
	int i;
	u8 val = 0;
	cs_low();
	clk_high();
	for(i=0; i<8; i++){
		val <<= 1;
		udelay(3);
		clk_high();
		udelay(3);
		clk_low();
		if(get_din()){
			val |= 1;
		}
	}
	clk_high();
	cs_high();
	return val;
}

static u8 cws[16] = {0x00, 0x08, 0x04, 0x0c, 0x02, 0x0a, 0x06, 0x0e, 0x01, 0x09, 0x05, 0x0d, 0x03, 0x0b, 0x07, 0x0f};

void WriteRegister(u8 chip_id,u8 chan, u8 addr, u8 dat)
{
	u8 cw = cws[chan] | 0x20;
	spi_write_byte(cw);
	udelay(10);
	spi_write_byte(addr);
	udelay(10);
	spi_write_byte(dat);

}

void ReadRegister(u8 chip_id,u8 chan, u8 addr, u8 *dat)
{
	u8 cw = cws[chan] | 0x60;
	spi_write_byte(cw);
	udelay(10);
	spi_write_byte(addr);
	udelay(10);
	*dat = spi_read_byte();
}
// si3050 sysfs interface @{
static ssize_t si3050_show(struct device *dev,struct device_attribute *attr, char *buf)
{
	int i, n=0;
	u8 dat;
	dev_info(dev, "%s\n", __func__);
	n = sprintf(buf, "si3050 dump\n");
	clk_prepare_enable(mclk);
	for(i=0; i<=SPRK_QNCH_CTRL; i++){
		ReadRegister(0, 0, i, &dat);
		msleep(5);
		n += sprintf(buf+n, "Reg:0x%x:0x%x\n", i, dat);
	}
	clk_disable_unprepare(mclk);
	return n;
}

static ssize_t si3050_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
	int chip, chan, addr, val;
	u8 dat;
	int ret = 0;
	clk_prepare_enable(mclk);
	ret = sscanf(buf, "%d %d %d %d", &chip, &chan, &addr, &val);
	if(ret < 4){
		ret = sscanf(buf, "%d %d %d", &chip, &chan, &addr);
		if(ret == 3){
			ReadRegister(chip, chan, addr, &dat);
			dev_info(dev, "%s read 0x%x: 0x%x\n", __func__, addr, dat);
		}else{
			dev_err(dev, "Input Error: ret=%d \n", ret);
			return -EINVAL;
		}
	}else{
		WriteRegister(chip, chan, addr, val);
		ReadRegister(chip, chan, addr, &dat);
		dev_info(dev, "%s write 0x%x: 0x%x, readback: 0x%x\n", __func__, addr, val, dat);
	}
	clk_disable_unprepare(mclk);
	return count;
}

static ssize_t clk_show(struct device *dev,struct device_attribute *attr, char *buf)
{
	return sprintf(buf, "%s echo 1/0 to enable/disable clock\n", __func__);
}

static ssize_t clk_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
	long val;
	int err = 0;
	err = kstrtol(buf, 10, &val);
	if(!err){
		if (val==0){
			clk_prepare_enable(mclk);
		}else{
			clk_disable_unprepare(mclk);
		}
	}
	return count;
}
static DEVICE_ATTR(debug, 0644, si3050_show, si3050_store);
static DEVICE_ATTR(clk, 0644, clk_show, clk_store);
// @}

// si3050 codec related @{
static int si3050_codec_probe(struct snd_soc_codec *codec)
{
	// TODO: add your operation
	dev_info(codec->dev, "%s\n", __func__);
	return 0;
}

static int si3050_codec_remove(struct snd_soc_codec *codec)
{
	// TODO: add your operation
	dev_info(codec->dev, "%s\n", __func__);
	return 0;
}

static struct snd_soc_codec_driver soc_codec_device_si3050 = {
	.probe =	si3050_codec_probe,
	.remove =	si3050_codec_remove,
};


int si3050_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir)
{
	struct snd_soc_codec *codec = codec_dai->codec;
	struct device * dev = codec->dev;
//	struct si3050_data *priv = snd_soc_codec_get_drvdata(codec);

	dev_info(dev, "clk_id=%d, freq=%u, dir=%d\n", clk_id, freq, dir);
	return 0;
}

static int si3050_set_dai_fmt(struct snd_soc_dai *codec_dai,
			      unsigned int format)
{
	struct snd_soc_codec *codec = codec_dai->codec;
	struct device * dev = codec->dev;
	dev_info(dev, "%s format=%d\n", __func__, format);
	return 0;
}

static int si3050_hw_params(struct snd_pcm_substream *substream,
			    struct snd_pcm_hw_params *params,
			    struct snd_soc_dai *codec_dai)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_soc_codec *codec = rtd->codec;
	struct device * dev = codec->dev;

	dev_info(dev, "%s\n", __func__);
	return 0;
}

static struct snd_soc_dai_ops si3050_dai_ops = {
	.hw_params = si3050_hw_params,
	.set_fmt = si3050_set_dai_fmt,
	.set_sysclk = si3050_set_dai_sysclk,
};

#define SI3050_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 )

static struct snd_soc_dai_driver si3050_dai = {
	.name = "si3050 GvDAA",
	.playback = {
		.stream_name = "Playback",
		.channels_min = 1,
		.channels_max = 2,
		.rates = SI3050_RATES,
		.formats =SNDRV_PCM_FMTBIT_S16_LE
	},
	.capture = {
		.stream_name = "Capture",
		.channels_min = 1,
		.channels_max = 2,
		.rates = SI3050_RATES,
		.formats = SNDRV_PCM_FMTBIT_S16_LE,
	},
	.ops = &si3050_dai_ops,
};
// si3050 codec related @}

// interrupt related function @{
static irqreturn_t si3050_irq(int irq, void *dev_id)
{
	struct platform_device * pdev = dev_id;
	dev_info(&pdev->dev, "%s called\n", __func__);
//	disable_irq_nosync(priv->irq);
	return IRQ_HANDLED;
}
// @}
#define MID_NAME "mid"
#define COMPATILE_NAME "mediatek,mid"
static const struct of_device_id mid_of_match[] = {
	{ .compatible = COMPATILE_NAME, },
	{},
};

static int mid_probe(struct platform_device *pdev)
{
	struct device * dev = &pdev->dev;
	struct device_node *node = dev->of_node;
	int err = 0;
	dev_info(dev, "%s\n", __func__);
	if((reset_pin=of_get_named_gpio(node, "reset", 0)) < 0){
		dev_err(dev, "of_get_named_gpio: %s fail, err=%d\n", "reset", reset_pin);
	}else{
		gpio_request(reset_pin, "reset_pin");
		gpio_direction_output(reset_pin, 1);
	}

	if((cs=of_get_named_gpio(node, "cs", 0)) < 0){
		dev_err(dev, "of_get_named_gpio: %s fail, err=%d\n", "cs", cs);
	}
	gpio_request(cs, "cs");
	if((clk=of_get_named_gpio(node, "clk", 0)) < 0){
		dev_err(dev, "of_get_named_gpio: %s fail, err=%d\n", "clk", cs);
	}
	gpio_request(clk, "clk");
	if((din=of_get_named_gpio(node, "din", 0)) < 0){
		dev_err(dev, "of_get_named_gpio: %s fail, err=%d\n", "din", cs);
	}
	gpio_request(din, "din");
	if((dout=of_get_named_gpio(node, "dout", 0)) < 0){
		dev_err(dev, "of_get_named_gpio: %s fail, err=%d\n", "dout", cs);
	}
	gpio_request(dout, "dout");

	err = device_create_file(&pdev->dev, &dev_attr_debug);
	if(err < 0){
		dev_err(dev, "device_create_file fail err=%d\n", err);
	}
// interrupt related function @{
	irq_pin = of_get_named_gpio_flags(node, "int-gpio", 0, NULL);
	if (!gpio_is_valid(irq_pin)) {
		dev_err(dev, "get int-gpio fail\n");
	}else{
		irq = gpio_to_irq(irq_pin);
		err = devm_request_threaded_irq(dev, irq, NULL, si3050_irq, IRQF_TRIGGER_RISING | IRQF_ONESHOT, "si3050", pdev);
		if (err != 0) {
			dev_err(dev, "Cannot allocate si3050 INT!ERRNO:%d\n", err);
		}
		enable_irq_wake(irq); // wake up system
	}
// @}

	err = device_create_file(&pdev->dev, &dev_attr_clk);
	if(err < 0){
		dev_err(dev, "device_create_file fail err=%d\n", err);
	}
	gpio_direction_output(cs, 1);
	gpio_direction_output(clk, 1);
	gpio_direction_output(dout, 1);
	gpio_direction_input(din);
	mclk = devm_clk_get(dev, "mclk");
	if(IS_ERR(mclk)){
		dev_err(dev, "devm_clk_get mclk failed, err=%ld\n", PTR_ERR(mclk));
		mclk = NULL;
	}else{
	//	clk_prepare_enable(mclk);
	}
// init is very import, otherwise spi not work
	gpio_set_value(reset_pin, 0);
	clk_prepare_enable(mclk);
	gpio_set_value(cs, 0);
	msleep(1);
	gpio_set_value(reset_pin, 1);
//
	si3050_setup();
	clk_disable_unprepare(mclk);

	err = snd_soc_register_codec(&pdev->dev, &soc_codec_device_si3050, &si3050_dai, 1);
	if(err < 0) {
		dev_err(&pdev->dev, "snd_soc_register_codec fail, err=%d\n", err);
	}
	return 0;
}

static int mid_remove(struct platform_device *pdev)
{
	struct device * dev = &pdev->dev;
	dev_info(dev, "%s\n", __func__);
	device_remove_file(&pdev->dev, &dev_attr_debug);
	gpio_free(cs);
	gpio_free(clk);
	gpio_free(din);
	gpio_free(dout);

	return 0;
}

static struct platform_driver mid_driver = {
	.probe = mid_probe,
	.remove = mid_remove,
	.driver = {
		.name = MID_NAME,
		.owner = THIS_MODULE,
		.of_match_table = of_match_ptr(mid_of_match)
	}
};


static int __init mid_init(void)
{
	pr_err("Register mid driver init\n");

	if (platform_driver_register(&mid_driver)) {
		pr_err("mid: failed to register disp driver\n");
		return -ENODEV;
	}

	return 0;
}

static void __exit mid_exit(void)
{

	platform_driver_unregister(&mid_driver);

	pr_err("Unregister mid driver done\n");
}
//module_init(mid_init);
late_initcall(mid_init);
module_exit(mid_exit);
MODULE_AUTHOR("MID");
MODULE_DESCRIPTION("Mid dts Driver");
MODULE_LICENSE("GPL");
